package de.tuhh.james.weka;

import static org.junit.Assert.assertTrue;

import hr.irb.fastRandomForest.FastRandomForest;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import weka.classifiers.bayes.BayesNet;
import weka.classifiers.bayes.NaiveBayes;
import weka.classifiers.functions.MultilayerPerceptron;
import weka.classifiers.functions.SMO;
import weka.classifiers.functions.SimpleLogistic;
import weka.classifiers.lazy.IBk;
import weka.classifiers.lazy.KStar;
import weka.classifiers.meta.Vote;
import weka.classifiers.trees.RandomForest;
import weka.core.Attribute;
import weka.core.Instances;
import weka.core.Utils;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.unsupervised.attribute.MathExpression;
import weka.filters.unsupervised.attribute.ReplaceMissingValues;
import weka.filters.unsupervised.instance.Normalize;

public class AlgorithmsTest {
	
	private Attribute classLabels = null;
	private ClassfierUtils classifierUtils = null;
	private static Transformer t = new Transformer();
	
	private static Logger logger =  LoggerFactory.getLogger(AlgorithmsTest.class);
	
	private static double currentBestPctCorrection = 0d;
	
	private static double[][] distanceMatrix_E = {
		{0,21.2,23.1,0,11.3,27,15,39.1,19.2,29.8,13.2},//4071
		{21.2,0,7,0,32.1,9,8.4,18.1,6.6,8.6,8.5},//4088
		{23.1,7,0,0,34.4,3.8,8,17,3.9,9,13.1},//4077
		{0,0,0,0,0,0,0,0,0,0,0},//unknown
		{11.3,32.1,34.4,0,0,38.3,26.5,50,30.5,40.8,23.7},//4065
		{27,9,3.8,0,38.3,0,11.9,13.6,7.8,6.9,16.4},//4078
		{15,8.4,8,0,26.5,11.9,0,24.5,4,15.5,7.5},//4075
		{39.1,18.1,17,0,50,13.6,24.5,0,20.9,9.3,26.6},//4084
		{19.2,6.6,3.9,0,30.5,7.8,4,20.9,0,12.1,10},//4076
		{29.8,8.6,9,0,40.8,6.9,15.5,9.3,12.1,0,17.1},//4086
		{13.2,8.5,13.1,0,23.7,16.4,7.5,26.6,10,17.1,0},//4091
	};
	

	@BeforeClass 
	public static void generateArff()throws Exception{
		logger.info(" Load the orignal file and generate new file with new data format.");
//		t.readFile(new File("data_input/rssi_E_samsung.arff"));
//		t.readFile(new File("data_input/rssi_E_Desire.arff"));
//		t.readFile(new File("data_input/rssi_desire.arff"));
		t.readFile(new File("data_input/baseset.arff"));
		int missingValue = 0;
		logger.debug("The missing value has been set to "+missingValue);
		t.setMiisingValue(missingValue);
		t.setTestDataPct(5);
//		t.transform(new File("data_output/train.arff"),new File("data_output/test.arff"));
		logger.info("DataSet has been generated.");
	}
	
	@Before
	public void setUp() throws Exception {
		classifierUtils = new ClassfierUtils();
		//=======================================================
		//add some filter here
		String optionString = null;
		
		
//		MathExpression mathFiler = new MathExpression();
//		mathFiler.setExpression("A/MAX");
//		classifierUtils.addFilter(mathFiler);
		
//		Normalize instanceNormalizeFilter = new Normalize();
//		optionString = "-N 1.0 -L 2.0";
//		instanceNormalizeFilter.setOptions(Utils.splitOptions(optionString));
//		classifierUtils.addFilter(instanceNormalizeFilter);
		
//		Normalize attributeNormalizeFilter = new Normalize();
//		attributeNormalizeFilter.setScale(1.0);
//		attributeNormalizeFilter.setTranslation(0d);
//		classifierUtils.addFilter(attributeNormalizeFilter);
		//=========================================================
		classifierUtils.setTrainingDataFile(new File("data_output/Derived_Set_5/train.arff"));
		classifierUtils.setTestingDataFile(new File("data_output/Derived_Set_5/test.arff"));
		classLabels = classifierUtils.getClassLabels();
	}

	@After
	public void tearDown() throws Exception {
//		List<String> lines = FileUtils.readLines(new File("report/report.txt"));
//		lines.add(ClassfierUtils.doubleToString(currentBestPctCorrection, 2));
//		FileUtils.writeLines(new File("report/report.txt"), lines);
		logger.info("The current best correction in percentage is "+currentBestPctCorrection);
	}
	
	//@AfterClass
	public static void delArff()throws Exception{
		FileUtils.cleanDirectory(new File("data_output"));
		logger.info("data_output dir has been cleaned.");
	}
	
	
	
	private void setBest(double d){
		currentBestPctCorrection = currentBestPctCorrection>d?currentBestPctCorrection:d;
	}
	

	@Test
	public void RandomForestTest() throws Exception {
		logger.info("--------------------------------------------------");
		logger.info("|Performing random forest classfication...       |");
		logger.info("--------------------------------------------------");
//		String optionString  = "-I 10 -K 0 -S ";
		String optionString  = "-I 50 -K 0 -S 0";
		List<Double> pctCorrectionList = new ArrayList<Double>();
//		for(int i=1;i<11;i++){
//			classifierUtils.setClassifier(RandomForest.class, Utils.splitOptions(optionString+i));
//			logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
//			double pctCorrection = classifierUtils.getPctCorretion();
//			pctCorrectionList.add(pctCorrection);
//			logger.info("For the test with seed "+i+", the correct classification percentage is "+pctCorrection);
//			double ooe = ((RandomForest)classifierUtils.getClassifier()).measureOutOfBagError();
//			logger.debug("\tand the out of bage error rate is "+ooe);
//		}
		classifierUtils.setClassifier(RandomForest.class, Utils.splitOptions(optionString));
		logger.info("The model build time is "+classifierUtils.getBuildModelTime());
		double avePctCorrection = classifierUtils.getPctCorretion();
//		double avePctCorrection = ClassfierUtils.getAverage(pctCorrectionList);
//		double stdDiv = ClassfierUtils.getStDvi(pctCorrectionList);
		logger.info("The average correction percentage is "+avePctCorrection);
//		logger.info("The standard deviation of  correction percentage is "+stdDiv);
		setBest(avePctCorrection);
		double mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
		logger.info("The error in meter is "+mError);
		
		int classRow = 0;
		for(double[] matrixRow: classifierUtils.getPctConfutionMatrix()){
			for(double d:matrixRow){
				System.out.printf("%1$.2f\t",d);
			}
			System.out.print(" | "+classLabels.value(classRow));
			System.out.println();
			classRow+=1;
		}
		classifierUtils.getModel(new File("data_output/randomForest.model"));
	}
	
	@Test
	public void FastRandomForestTest() throws Exception {
		logger.info("--------------------------------------------------");
		logger.info("|Performing fast random forest classfication...   |");
		logger.info("--------------------------------------------------");
//		String optionString  = "-I 50 -K 0 -S ";
		String optionString  = "-I 50 -K 0 -S 0";
		List<Double> pctCorrectionList = new ArrayList<Double>();
//		for(int i=1;i<11;i++){
//			classifierUtils.setClassifier(FastRandomForest.class, Utils.splitOptions(optionString+i));
//			logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
//			double pctCorrection = classifierUtils.getPctCorretion();
//			pctCorrectionList.add(pctCorrection);
//			logger.info("For the test with seed "+i+", the correct classification percentage is "+pctCorrection);
//			double ooe = ((FastRandomForest)classifierUtils.getClassifier()).measureOutOfBagError();
//			logger.debug("\tand the out of bage error rate is "+ooe);
//		}
//		double avePctCorrection = ClassfierUtils.getAverage(pctCorrectionList);
//		double stdDiv = ClassfierUtils.getStDvi(pctCorrectionList);
		classifierUtils.setClassifier(FastRandomForest.class, Utils.splitOptions(optionString));
		logger.info("The model build time is "+classifierUtils.getBuildModelTime());
		double avePctCorrection = classifierUtils.getPctCorretion();
		logger.info("The average correction percentage is "+avePctCorrection);
//		logger.info("The standard deviation of  correction percentage is "+stdDiv);
		setBest(avePctCorrection);
		double mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
		logger.info("The error in meter is "+mError);
		
//		int classRow = 0;
//		for(double[] matrixRow:confutionMatrix){
//			for(double d:matrixRow){
//				System.out.printf("%1$.2f\t",d);
//			}
//			System.out.print(" | "+classLabels.value(classRow));
//			System.out.println();
//			classRow+=1;
//		}
//		classifierUtils.getModel(new File("data_output/randomForest.model"));
	}
	
	@Test
	public void KNNTest()throws Exception{
		logger.info("--------------------------------------------------");
		logger.info("|Performing N nearest neighbourhood classification|");
		logger.info("--------------------------------------------------");
		String[] distanceFunctions = {"EuclideanDistance"};
		for(String distanceFunction:distanceFunctions){
			logger.info("============= using "+distanceFunction+" ================");
			logger.info("Searching nearest neighbourhood with linear brut force search.");
			String optionString = "-K 10 -W 0 -A \"weka.core.neighboursearch.LinearNNSearch -S -A \\\"weka.core."+distanceFunction+" -R first-last\\\"\"";
			classifierUtils.setClassifier(IBk.class, Utils.splitOptions(optionString));
			double pctCorrection = classifierUtils.getPctCorretion();
			logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
			logger.info("\tThe correction percentage is "+pctCorrection);
			setBest(pctCorrection);
			double mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
			logger.info("The error in meter is "+mError);
			logger.info("------------------------------------------------------------------------");
//			if(distanceFunction.equalsIgnoreCase("EuclideanDistance")){
				// the following three nn search function only supports Euclidean distance
				logger.info("Searching nearest neighbourhood with cover tree search.");
				optionString = "-K 10 -I -W 0 -A \"weka.core.neighboursearch.CoverTree -A \\\"weka.core."+distanceFunction+" -R first-last\\\" -B 1.3\"";
				classifierUtils.setClassifier(IBk.class, Utils.splitOptions(optionString));
				logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
				pctCorrection = classifierUtils.getPctCorretion();
				logger.info("\tThe correction percentage is "+pctCorrection);
				setBest(pctCorrection);
				mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
				logger.info("The error in meter is "+mError);
				logger.info("------------------------------------------------------------------------");
			
				logger.info("Searching nearest neighbourhood with KD tree search.");
				optionString = "-K 20 -I -W 0 -A \"weka.core.neighboursearch.KDTree -A \\\"weka.core."+distanceFunction+" -R first-last\\\" -S weka.core.neighboursearch.kdtrees.SlidingMidPointOfWidestSide -W 0.01 -L 40 -N\"";
				classifierUtils.setClassifier(IBk.class, Utils.splitOptions(optionString));
				logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
				pctCorrection = classifierUtils.getPctCorretion();
				logger.info("\tThe correction percentage is "+pctCorrection);
				setBest(pctCorrection);
				mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
				logger.info("The error in meter is "+mError);
				logger.info("------------------------------------------------------------------------");
				
				logger.info("Searching nearest neighbourhood with ball tree search.");
				optionString = "-K 10 -I -W 0 -A \"weka.core.neighboursearch.BallTree -A \\\"weka.core."+distanceFunction+" -R first-last\\\" -C \\\"weka.core.neighboursearch.balltrees.TopDownConstructor -N 40 -S weka.core.neighboursearch.balltrees.PointsClosestToFurthestChildren\\\"\"";
				classifierUtils.setClassifier(IBk.class, Utils.splitOptions(optionString));
				logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
				pctCorrection = classifierUtils.getPctCorretion();
				logger.info("\tThe correction percentage is "+pctCorrection);
				setBest(pctCorrection);
				mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
				logger.info("The error in meter is "+mError);
//			}
		}
	}
	
	//@Test
	public void KStarTest()throws Exception{
		//FIXIT known issue, Kstar cant handle mixed dataset
		//the advantage of this classifier is that it's updatable
		logger.info("--------------------------------------------------");
		logger.info("|Performing KStar classification.                 |");
		logger.info("--------------------------------------------------");
		String optionString = "-B 20 -M a";
		classifierUtils.setClassifier(KStar.class, Utils.splitOptions(optionString));
		logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
		double pctCorrection = classifierUtils.getPctCorretion();
		logger.info("The correction percentage is "+pctCorrection);
		setBest(pctCorrection);
	}
	
	@Test
	public void VoteTest()throws Exception{
		//TODO the result of this test is different from that of weka explorer
		logger.info("--------------------------------------------------");
		logger.info("|Performing Vote classification.                 |");
		logger.info("--------------------------------------------------");
//		String optionString = "-S 1 -R MAJ -B \"weka.classifiers.trees.RandomForest -I 50 -K 0 -S 1\" -B \"weka.classifiers.lazy.IBk -K 1 -W 0 -A \\\"weka.core.neighboursearch.LinearNNSearch -A \\\\\\\"weka.core.ManhattanDistance -R first-last\\\\\\\"\\\"\" -B \"weka.classifiers.bayes.NaiveBayes \"";
		String optionString = "-S 1 -R MAX -B \"hr.irb.fastRandomForest.FastRandomForest -I 20 -K 0 -S 1\" -B \"weka.classifiers.lazy.IBk -K 10 -W 0 -A \\\"weka.core.neighboursearch.KDTree -A \\\\\\\"weka.core.EuclideanDistance -R first-last\\\\\\\"\\\"\" -B \"weka.classifiers.bayes.NaiveBayes \" -B \"weka.classifiers.functions.SimpleLogistic -I 0 -M 500 -H 50 -W 0.0\" -B \"weka.classifiers.functions.SMO -C 1.0 -L 0.001 -P 1.0E-12 -N 0 -V -1 -W 1 -K \\\"weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0\\\"\"";
		classifierUtils.setClassifier(Vote.class, Utils.splitOptions(optionString));
		logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
		double pctCorrection = classifierUtils.getPctCorretion();
		logger.info("The correction percentage is "+pctCorrection);
		setBest(pctCorrection);
		
		logger.info("try to measure error in meter.");
		double[][] pctConfutionMatrix = classifierUtils.getPctConfutionMatrix();
//		int classRow = 0;
//		for(double[] matrixRow:pctConfutionMatrix){
//			for(double d:matrixRow){
//				System.out.printf("%1$.2f\t",d);
//			}
//			System.out.print(" | "+classLabels.value(classRow));
//			System.out.println();
//			classRow+=1;
//		}
		double meterError = classifierUtils.getMeterError(distanceMatrix_E, pctConfutionMatrix);
		logger.info("The error in meter is " +ClassfierUtils.doubleToString(meterError, 2));
		
//		logger.info("Try to find classfied result.");
//		Instances testData = classifierUtils.getTestData();
//		if(testData ==null){
//			assertTrue("Not test data found.",false);
//		}
//		ClassifyResult classifyResult = null;
//		for (int i = 0; i < testData.numInstances(); i++) {
//			 classifyResult = classifierUtils.classifiy(testData.instance(i));
//			 logger.info("For the "+(i+1)+"th test data, the result is "+classifyResult.toString()+", while the real class lable is "+classLabels.value((int)testData.instance(i).classValue())+". So reslut is "+(classifyResult.getClassLableDouble()==testData.instance(i).classValue()));
//		}
	}
	
	
	@Test
	public void NaiveBayesTest()throws Exception{
		logger.info("--------------------------------------------------");
		logger.info("|Performing NaiveBayes classification.           |");
		logger.info("--------------------------------------------------");
		logger.warn("WARNING : The performence of the model varies much regards to different train and test dataset, so the correction rate shown below is not reliable.");
		String optionString = "";
		classifierUtils.setClassifier(NaiveBayes.class, Utils.splitOptions(optionString));
		logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
		double pctCorrection = classifierUtils.getPctCorretion();
		logger.info("The correction percentage is "+pctCorrection);
		setBest(pctCorrection);
		double mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
		logger.info("The error in meter is "+mError);
	}
	
	//@Test
	public void BayesNetTest()throws Exception{
		//FIXIT known issue, the bayesNet is unknown
		logger.info("--------------------------------------------------");
		logger.info("|Performing BayersNet classification.            |");
		logger.info("--------------------------------------------------");
		logger.warn("WARNING : The performence of the model varies much regards to different train and test dataset, so the correction rate shown below is not reliable.");
		String optionString = "-D -Q weka.classifiers.bayes.net.search.local.K2 -- -P 1 -S BAYES -E weka.classifiers.bayes.net.estimate.SimpleEstimator -- -A 0.5";
		classifierUtils.setClassifier(BayesNet.class, Utils.splitOptions(optionString));
		logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
		double pctCorrection = classifierUtils.getPctCorretion();
		logger.info("The correction percentage is "+pctCorrection);
		setBest(pctCorrection);
	}
	
	@Test
	public void SMOTest()throws Exception{
		//TODO known issure, not really stable with different train and test data
		logger.info("--------------------------------------------------");
		logger.info("|Performing SMO classification.                   |");
		logger.info("--------------------------------------------------");
		String optionString = "-C 1.0 -L 0.001 -P 1.0E-12 -N 0 -V -1 -W 1 -K \"weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0\"";
		classifierUtils.setClassifier(SMO.class, Utils.splitOptions(optionString));
		logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
		double pctCorrection = classifierUtils.getPctCorretion();
		logger.info("The correction percentage is "+pctCorrection);
		setBest(pctCorrection);
		double mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
		logger.info("The error in meter is "+mError);
	}
	
	@Test
	public void SimpleLogisticTest()throws Exception{
		//TODO known issure, takes realy long to build the model, especially for mixed dataset
		logger.info("--------------------------------------------------");
		logger.info("|Performing SimpleLogistic classification.        |");
		logger.info("--------------------------------------------------");
		logger.warn("The classification procedure can be extremly long!!! Be paticent.");
		String optionString = "-I 0 -M 500 -H 50 -W 0.0";
		classifierUtils.setClassifier(SimpleLogistic.class, Utils.splitOptions(optionString));
		logger.info("Time spend to build the model is "+classifierUtils.getBuildModelTime()+" second.");
		double pctCorrection = classifierUtils.getPctCorretion();
		logger.info("The correction percentage is "+pctCorrection);
		setBest(pctCorrection);
		double mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
		logger.info("The error in meter is "+mError);
	}

	@Test
	public void MultilayerPerceptionTest() throws Exception {
		// TODO known issure, takes realy long to build the model, especially
		// for mixed dataset
		logger.info("--------------------------------------------------");
		logger.info("|Performing MultilayerPerception classification.        |");
		logger.info("--------------------------------------------------");
		logger.warn("The classification procedure can be extremly long!!! Be paticent.");
		String optionString = "-L 0.3 -M 0.2 -N 500 -V 0 -S 0 -E 20 -H a";
		classifierUtils.setClassifier(MultilayerPerceptron.class,
				Utils.splitOptions(optionString));
		logger.info("Time spend to build the model is "
				+ classifierUtils.getBuildModelTime() + " second.");
		double pctCorrection = classifierUtils.getPctCorretion();
		logger.info("The correction percentage is " + pctCorrection);
		setBest(pctCorrection);
		double mError = classifierUtils.getMeterError(distanceMatrix_E, classifierUtils.getPctConfutionMatrix());
		logger.info("The error in meter is "+mError);
	}
	
	//@Test
	public void test()throws Exception{
		List<String> BSSIDList = new ArrayList<String>();
		if(BSSIDList.size()==0){
			try {
				List<String> lines = FileUtils.readLines(new File("data_output/train.arff"));
				for(String line:lines){
					if(line.startsWith("@attribute")){
						String[] part = line.split(" ");
						if(!"ROOM".equalsIgnoreCase(part[1])){
							BSSIDList.add(part[1]);
							logger.info("Find one BSSID from structure file - "+part[1]);
						}
					}
				}
			} catch (IOException e) {
				logger.equals(e);
			}
		}
		logger.info("The size of the BSSIDList is "+BSSIDList.size());
		
	}

}
